package com.yc.spring.mvc.web;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class MyDispathServlet extends HttpServlet {
    AnnotationConfigApplicationContext context;

    public MyDispathServlet(AnnotationConfigApplicationContext context) {
        this.context = context;
    }
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("req.getServletPath() = " + req.getServletPath());

        //根据请求地址从IOC查找控制器方法
        Handler handler = handlerMapping(req.getServletPath()); 
        System.out.println("handler = " + handler);
        //执行控制器方法
        if(handler == null){
            //判断是否是静态资源
            if(isStaticResource(req,resp)){
                //静态资源
                processStaticResource(req,resp);
            }else{
                //404
            }
        }else {
            //执行控制器方法
            Object ret=executeHandler(handler,req,resp);
            //处理控制器方法的返回结果json 页面跳转
            boolean isRestControol=handler.getController().getClass().getAnnotation(RestController.class)!=null;
            boolean isResponseBody=handler.getMethod().getAnnotation(ResponseBody.class)!=null;
            if(isRestControol||isResponseBody){
                ObjectMapper objectMapper=new ObjectMapper();
                String json=objectMapper.writeValueAsString(ret);
                resp.setContentType("application/json;charset=utf-8");
                resp.setCharacterEncoding("utf-8");
                resp.getWriter().println(json);
            }
        }

    }

    private Object executeHandler(Handler handler, HttpServletRequest req, HttpServletResponse resp) {
        //获取控制器方法的参数列表
        Parameter[] parameters=handler.getMethod().getParameters();
        List<Object> paramValues=new ArrayList<>();

        LocalVariableTableParameterNameDiscoverer lv=new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames=lv.getParameterNames(handler.method);
        int i=0;
        for(Parameter parameter:parameters){
            //没有获取到参数名
//            String name=parameter.getName();
            String name=parameterNames[i++];
            String svalue=req.getParameter(name);
            //参数类型
            Class<?> type=parameter.getType();
            Object ovalue=svalue;
            if(type.isAssignableFrom(Integer.class)||type.isAssignableFrom(int.class)){
                ovalue=Integer.parseInt(svalue);
            }else if(type.isAssignableFrom(Boolean.class)||type.isAssignableFrom(boolean.class)){
                ovalue=Boolean.valueOf(svalue);
            }else if(type.isAssignableFrom(String.class)){
                //字符串不用处理
            }else if(type.isAssignableFrom(HttpServletRequest.class)){
                ovalue=req;
            }else if(type.isAssignableFrom(HttpServletResponse.class)){
                ovalue=resp;
            }else if(type.isAssignableFrom(HttpSession.class)){
                ovalue=req.getSession();
            }else if(type.isAssignableFrom(ModelAndView.class)){
                ovalue=new ModelAndView();
            }else{//基础类型处理完后，剩下的都是实体对象
                //实体对象User
                //使用反射将请求参数设置到对象属性中
                try {
                    Object o=parameter.getType().newInstance();
                    for (Field field : o.getClass().getDeclaredFields()) {
                        String filedName=field.getName();
                        String filedSvalue=req.getParameter(filedName);
                        if(filedSvalue==null){
                            continue;
                        }
                        Object filedOvalue=filedSvalue;
                        if(field.getType().isAssignableFrom(int.class)){
                            filedOvalue=Integer.parseInt(filedSvalue);
                        }else{
                            //其他类型自行扩展
                        }
                        field.setAccessible(true);
                        field.set(o,filedOvalue);
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
            paramValues.add(ovalue);
        }
        //执行控制器方法
        handler.method.setAccessible(true);
        try {
            Object ret=handler.method.invoke(handler.controller,paramValues.toArray());
            return ret;
        } catch (Exception e) {
            throw new RuntimeException("执行控制器方法错误",e);
        }
    }
    private void processStaticResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String webpath = req.getServletPath();
        String filepath=req.getServletContext().getRealPath(webpath);
        try(FileInputStream fis=new FileInputStream(filepath)){
            ServletOutputStream out=resp.getOutputStream();
            byte[] bytes=new byte[1024];
            int count;
            while((count=fis.read(bytes))!=-1){
                out.write(bytes,0,count);
            }
        }catch (IOException e){
            throw new RuntimeException("读取资源文件错误",e);
        }
    }

    private boolean isStaticResource(HttpServletRequest req, HttpServletResponse resp) {
        //webPath=/index.html
        String webPath = req.getServletPath();
        //getRealPath将web静态资源转为磁盘资源
        String filePath=req.getServletContext().getRealPath(webPath);
        //判断文件是否存在
        return new File(filePath).exists();
    }

    private Handler handlerMapping(String path){
        Map<String, Object> restControllers = context.getBeansWithAnnotation(RestController.class);
        Map<String, Object> controllers = context.getBeansWithAnnotation(Controller.class);
        controllers.putAll(restControllers);
       //搜索控制器方法+控制器对象
        for (Object controller : controllers.values()) {
            String prefixPath = null;
            RequestMapping requestMapping=controller.getClass().getAnnotation(RequestMapping.class);
            if(requestMapping!=null){
                prefixPath=requestMapping.value()[0];
            }
            for (Method method : controller.getClass().getDeclaredMethods()) {
                String suffixPath;
                RequestMapping requestMapping1=method.getAnnotation(RequestMapping.class);
                GetMapping getMapping=method.getAnnotation(GetMapping.class);
                suffixPath = getMapping != null && getMapping.value().length > 0 ?
                        getMapping.value()[0] : null;
                if(suffixPath!=null){
                    String handlerPath="/"+suffixPath;
                    if (prefixPath != null) {
                        handlerPath="/"+prefixPath+handlerPath;
                    }
                    if(handlerPath.equals(path)){
                        Handler handler=new Handler(method,controller);
                        return handler;
                    }
                }
            }
        }

        return null;
    }
    @AllArgsConstructor
    @Data
    class Handler{
        //控制器方法
        Method method;
        //控制器对象
        Object controller;
    }
}
